package repository

import org.hibernate.criterion.Restrictions
import java.lang.Exception
import java.lang.reflect.ParameterizedType
import java.util.*

interface BaseRepository<T> {
    fun save(entity: T): T {
        executeTransaction { it.saveOrUpdate(entity) }
        return entity
    }

    fun delete(entity: T) {
        executeTransaction { it.delete(entity) }
    }

    fun findById(id: Int): Optional<T> {
        // val clazz = (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0] as Class<T>

        /**
         * For instance, interface UserRepository: BaseRepository<User>, class UserRepositoryImpl: UserRepository
         * val u = UserRepositoryImpl() as UserRepository
         * call javaClass to get clazz of UserRepositoryImpl, and than interfaces[0] to get UserRepository,
         * than call genericInterfaces[0] to get type-BaseRepository<T>, whose T has been filled with explicit type.
         * So we can cast it to ParameterizedType and than get Class<T>.
         *
         * In fact, it is much better to use recursion to get class of BaseRepository<T>.
         */
        val clazz = (javaClass.interfaces[0].genericInterfaces[0] as ParameterizedType).actualTypeArguments[0] as Class<T>
        clazz.name
        return executeTransaction {
            val cr = it.createCriteria(clazz)
            cr.add(Restrictions.idEq(id))
            Optional.ofNullable(cr.uniqueResult() as T)
        }
    }

    fun findAll(): List<T> {
        val clazz = (javaClass.interfaces[0].genericInterfaces[0] as ParameterizedType).actualTypeArguments[0] as Class<T>
        return executeTransaction { session ->
            val cr = session.createCriteria(clazz)
            cr.list().map { it as T }
        }
    }

    fun findAll(pageable: Pageable, entityName: String? = null): Page<T> {
        val clazz = (javaClass.interfaces[0].genericInterfaces[0] as ParameterizedType).actualTypeArguments[0] as Class<T>
        val tableName = entityName?:run { clazz.simpleName }
        return executeTransaction { session ->
            val cr = session.createCriteria(clazz).also {
                it.setFirstResult(pageable.pageNumber * pageable.pageSize)
                it.setMaxResults(pageable.pageSize)
            }

            Page(
                content = cr.list().map { it as T },
                pageable = pageable,
                totalElements = session.createQuery("SELECT COUNT(t) FROM $tableName t").uniqueResult() as Long
            )
        }
    }

    fun saveAll(entities: List<T>): List<T> {
        executeTransaction { session ->
            entities.forEach{ session.saveOrUpdate(it) }
        }
        return entities
    }

    fun deleteById(id: Int) {
        val entity = findById(id)
        if (entity.isPresent) {
            executeTransaction { it.delete(entity.get()) }
        } else {
            throw Exception()
        }
    }

    @Deprecated("No function body")
    fun deleteAll() {
        // Unnecessary
    }
}